package reader;

import exception.InvalidHtmlDocumentException;
import exception.UnsupportedTagException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

/**
 * Created by Pavel Nikolaev
 */

public abstract class HtmlReader {
    private final static int BUFFER_SIZE = 4096;

    enum LexerState {
        LEXER_STATE_TEXT,
        LEXER_STATE_TAG,
        LEXER_STATE_SKIP_SPACE,
        LEXER_STATE_PRE_TEXT,
        LEXER_STATE_PRE_WAS_CR
    }

    public void readDocument(Reader in) throws IOException, UnsupportedTagException, InvalidHtmlDocumentException {

        BufferedReader reader = new BufferedReader(in, BUFFER_SIZE);

	    handleDocumentStart();

        char[] buffer = new char[BUFFER_SIZE];
        int length = 0;
        int offset = 0;
        boolean preMode = false;
        //boolean lineStart = true;
        LexerState state = LexerState.LEXER_STATE_TEXT;
        String accumulatedText = "";
        do {
            length = reader.read(buffer, offset, BUFFER_SIZE);
	    	int startOffset = 0;
            for (int currentOffset = 0; currentOffset < length; ++currentOffset) {
                final char ch = buffer[currentOffset];

                if (ch == '<') {
                    if (startOffset != currentOffset) {
                        accumulatedText += new String(buffer, startOffset, currentOffset - startOffset);
                        startOffset = currentOffset;
                    }
                    if (!accumulatedText.isEmpty()) {
                        if (state == HtmlReader.LexerState.LEXER_STATE_SKIP_SPACE) accumulatedText += ' ';
                        handleText(accumulatedText);
                        accumulatedText = "";
                    }
                    state = LexerState.LEXER_STATE_TAG;
                    continue;
                }

                switch(state) {
                    case LEXER_STATE_TEXT:
                        if (Character.isWhitespace(ch)) {
                            if (startOffset != currentOffset) accumulatedText += new String(buffer, startOffset, currentOffset - startOffset);
                            // Replace sequence of the white spaces to the single space
                            //if(!accumulatedText.isEmpty()) accumulatedText += ' ';
                            startOffset = currentOffset + 1;
                            state = LexerState.LEXER_STATE_SKIP_SPACE;
                        }
                        break;

                    case LEXER_STATE_SKIP_SPACE:
                        if (Character.isWhitespace(ch)) {
                            startOffset = currentOffset + 1;
                        } else {
                            // Replace sequence of the white spaces to the single space
                            accumulatedText += ' ';
                            startOffset = currentOffset;
                            state = LexerState.LEXER_STATE_TEXT;
                        }
                        break;

                    case LEXER_STATE_PRE_TEXT:
                        if ((ch == '\r') || (ch == '\n')) {
                            if (startOffset != currentOffset) accumulatedText += new String(buffer, startOffset, currentOffset - startOffset);
                            if (!accumulatedText.isEmpty()) {
                                handleText(accumulatedText);
                                accumulatedText = "";
                            }
                            handleTag(new HtmlTag(HtmlTag.TagType.BR));

                            startOffset = currentOffset + 1;
                            if (ch == '\r') state = HtmlReader.LexerState.LEXER_STATE_PRE_WAS_CR;
                        }
                        break;

                    case LEXER_STATE_PRE_WAS_CR:
                        if (ch == '\r') {
                            handleTag(new HtmlTag(HtmlTag.TagType.BR));
                            startOffset = currentOffset + 1;
                        } else if (ch == '\n') {
                            startOffset = currentOffset + 1;
                            state = HtmlReader.LexerState.LEXER_STATE_PRE_TEXT;
                        } else {
                            startOffset = currentOffset;
                            state = HtmlReader.LexerState.LEXER_STATE_PRE_TEXT;
                        }
                        break;

                    case LEXER_STATE_TAG:
                        if (ch == '>') {
                            accumulatedText += new String(buffer, startOffset, currentOffset - startOffset + 1);
                            HtmlTag tag = new HtmlTag(accumulatedText);
                            handleTag(tag);
                            startOffset = currentOffset + 1;
                            accumulatedText = "";
                            if (tag.type == HtmlTag.TagType.PRE) preMode = tag.isOpen;
                            if (preMode) state = LexerState.LEXER_STATE_PRE_TEXT;
                            else state = LexerState.LEXER_STATE_TEXT;
                        }
                        break;
                }
            }

            if ((startOffset != length) &&
                    ((state == LexerState.LEXER_STATE_TEXT) ||
                            (state == LexerState.LEXER_STATE_TAG))) {
                        accumulatedText += new String(buffer, startOffset, length - startOffset);
            }
		    offset += length;
	    } while (length == BUFFER_SIZE);

        handleDocumentEnd();
    }

    protected abstract void handleDocumentStart() throws InvalidHtmlDocumentException;
    protected abstract void handleDocumentEnd() throws InvalidHtmlDocumentException;
    protected abstract void handleTag(HtmlTag tag) throws InvalidHtmlDocumentException;
    protected abstract void handleText(String text) throws InvalidHtmlDocumentException;
}
